/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package neuralnetwork;

/**
 * 神经网络模型评估工具类
 *
 * @author 朱婧雯
 * @see NeuralNetwork
 * @see Data
 */
public class Model {

    /**
     * 计算神经网络在给定数据集上的均方误差（MSE）
     * <p>
     * 均方误差是回归问题中最常用的损失函数，衡量预测值与真实值之间的平均平方差异。 公式：MSE = (1/n) × Σ(预测值 - 真实值)²
     * </p>
     *
     * @param network 待评估的神经网络
     * @param inputs 输入数据数组
     * @param targets 目标数据数组，与输入数据一一对应
     * @return 计算得到的均方误差值
     * @throws IllegalArgumentException 如果输入参数无效或维度不匹配
     *
     * <p>
     * <b>实现说明：</b> 计算过程： 1. 对每个输入样本进行前向传播得到预测值 2. 计算每个预测值与对应目标值的平方误差 3.
     * 累加所有误差并求平均值</p>
     *
     * <p>
     * <b>使用示例：</b></p>      <pre>
     * {@code
     * double mse = Model.calculateMSE(network, testInputs, testTargets);
     * System.out.printf("模型均方误差: %.6f%n", mse);
     * }
     * </pre>
     */
    public static double calculateMSE(NeuralNetwork network, double[][] inputs, double[][] targets) {
        // 参数验证
        if (network == null) {
            throw new IllegalArgumentException("神经网络不能为null");
        }
        if (inputs == null || targets == null) {
            throw new IllegalArgumentException("输入数据和目标数据不能为null");
        }
        if (inputs.length != targets.length) {
            throw new IllegalArgumentException("输入数据和目标数据数量不匹配");
        }
        if (inputs.length == 0) {
            throw new IllegalArgumentException("数据不能为空");
        }

        double totalError = 0;
        int count = 0;

        // 遍历所有样本计算总误差
        for (int i = 0; i < inputs.length; i++) {
            // 前向传播获取预测值
            double[] output = network.forward(inputs[i]);

            // 验证输出与目标维度匹配
            if (output.length != targets[i].length) {
                throw new IllegalArgumentException(
                        String.format("第%d个样本的输出维度(%d)与目标维度(%d)不匹配",
                                i, output.length, targets[i].length));
            }

            // 计算每个输出节点的平方误差
            for (int j = 0; j < output.length; j++) {
                double error = output[j] - targets[i][j];
                totalError += error * error;  // 平方误差
                count++;
            }
        }

        // 返回平均误差：总误差 / 总样本数
        return totalError / count;
    }

    /**
     * 全面评估神经网络模型的性能
     * <p>
     * 对训练好的神经网络进行综合评估，包括： 1. 计算测试集上的均方误差 2. 展示多个样本的详细预测结果 3. 对比预测值与真实值的差异
     * </p>
     *
     * @param network 待评估的神经网络模型
     * @param testInputs 测试集输入数据
     * @param testTargets 测试集目标数据
     * @param methodName 训练方法名称（用于结果标识）
     * @throws IllegalArgumentException 如果输入参数无效
     *
     * <p>
     * <b>实现说明： </b>评估流程： 1. 计算整体均方误差作为模型性能指标 2. 选择前5个样本展示详细预测结果 3.
     * 将归一化值反归一化回原始范围显示 4. 计算并显示每个样本的绝对误差</p>
     *
     * <p>
     * <b>使用示例：</b></p>      <pre>
     * {@code
     * Model.evaluate(network, testInputs, testTargets, "梯度下降法");
     *
     * // 输出示例：
     * // 梯度下降法 - 测试集均方差损失: 0.002345
     * // 样本预测结果:
     * // 输入: (0.10, -0.20), 目标: 0.2100, 预测: 0.2050, 误差: 0.0050
     * // ...
     * }
     * </pre>
     */
    public static void evaluate(NeuralNetwork network, double[][] testInputs,
            double[][] testTargets, String methodName) {
        // 参数验证
        if (methodName == null || methodName.trim().isEmpty()) {
            throw new IllegalArgumentException("方法名称不能为空");
        }
        if (network == null || testInputs == null || testTargets == null) {
            throw new IllegalArgumentException("网络和数据参数不能为null");
        }

        // 计算测试集均方误差
        double mse = calculateMSE(network, testInputs, testTargets);

        // 输出整体性能指标
        System.out.printf("\n%s - 测试集评估结果:%n", methodName);
        System.out.printf("  均方误差(MSE): %.6f%n", mse);
        System.out.printf("  均方根误差(RMSE): %.6f%n", Math.sqrt(mse));

        // 显示详细样本预测结果
        System.out.println("  样本预测详情:");
        displaySamplePredictions(network, testInputs, testTargets);
    }

    /**
     * 显示样本预测详情
     * <p>
     * 展示前几个测试样本的输入、目标值、预测值和误差， 帮助直观理解模型的预测能力。
     * </p>
     *
     * @param network 神经网络模型
     * @param inputs 输入数据
     * @param targets 目标数据
     */
    private static void displaySamplePredictions(NeuralNetwork network,
            double[][] inputs, double[][] targets) {
        // 显示前5个样本或所有样本（如果少于5个）
        int displayCount = Math.min(5, inputs.length);

        for (int i = 0; i < displayCount; i++) {
            // 获取预测值
            double[] prediction = network.forward(inputs[i]);

            // 反归一化到原始范围显示
            double actual = Data.denormalize(targets[i][0]);
            double predicted = Data.denormalize(prediction[0]);
            double absoluteError = Math.abs(actual - predicted);

            // 格式化输出
            System.out.printf("    样本%d: 输入(%.3f, %.3f), 目标: %.4f, 预测: %.4f, 误差: %.4f%n",
                    i + 1,
                    inputs[i][0], inputs[i][1], // 输入特征
                    actual, // 真实值（反归一化）
                    predicted, // 预测值（反归一化）
                    absoluteError);              // 绝对误差
        }

        // 如果还有更多样本，显示统计信息
        if (inputs.length > displayCount) {
            System.out.printf("    ... 还有%d个样本未显示%n", inputs.length - displayCount);
        }
    }

    /**
     * 计算模型的决定系数（R²）
     * <p>
     * R²分数表示模型解释的目标变量方差比例，取值范围[0,1]。 值越接近1表示模型拟合效果越好。
     * </p>
     *
     * @param network 神经网络模型
     * @param inputs 输入数据
     * @param targets 目标数据
     * @return R²决定系数
     */
    public static double calculateR2Score(NeuralNetwork network, double[][] inputs, double[][] targets) {
        if (inputs.length == 0) {
            return 0;
        }

        // 计算目标值的均值
        double targetMean = 0;
        for (double[] target : targets) {
            targetMean += target[0];
        }
        targetMean /= targets.length;

        // 计算总平方和
        double totalSumSquares = 0;
        for (double[] target : targets) {
            totalSumSquares += Math.pow(target[0] - targetMean, 2);
        }

        // 计算残差平方和
        double residualSumSquares = 0;
        for (int i = 0; i < inputs.length; i++) {
            double[] prediction = network.forward(inputs[i]);
            residualSumSquares += Math.pow(prediction[0] - targets[i][0], 2);
        }

        // 计算R²分数
        return 1 - (residualSumSquares / totalSumSquares);
    }

    /**
     * 生成模型评估报告
     * <p>
     * 创建包含多个评估指标的完整报告。
     * </p>
     *
     * @param network 神经网络模型
     * @param inputs 输入数据
     * @param targets 目标数据
     * @param methodName 训练方法名称
     * @return 格式化的评估报告字符串
     */
    public static String generateReport(NeuralNetwork network, double[][] inputs,
            double[][] targets, String methodName) {
        double mse = calculateMSE(network, inputs, targets);
        double rmse = Math.sqrt(mse);
        double r2 = calculateR2Score(network, inputs, targets);

        return String.format(
                "模型评估报告 - %s\n"
                + "====================\n"
                + "均方误差 (MSE):   %.6f\n"
                + "均方根误差(RMSE): %.6f\n"
                + "决定系数 (R²):    %.6f\n"
                + "样本数量:         %d",
                methodName, mse, rmse, r2, inputs.length
        );
    }
}
